﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Synchronization;

namespace Obra.Sync
{
    public class Orchestrator : 
        IDisposable
    {
        private readonly SyncProvider localProvider;

        public SyncProvider LocalProvider
        {
            get { return localProvider; }
        }

        private readonly SyncProvider remoteProvider;

        public SyncProvider RemoteProvider
        {
            get { return remoteProvider; }
        }

        /// <summary>
        /// c-tor
        /// </summary>
        /// <param name="syncProviderSource"></param>
        /// <param name="syncProviderTarget"></param>
        public Orchestrator(
            SyncProvider localProvider,
            SyncProvider remoteProvider)
        {
            this.localProvider = localProvider;
            this.remoteProvider = remoteProvider;

            this.RegisterEventHandlers(this.LocalProvider);
            this.RegisterEventHandlers(this.RemoteProvider);
        }

        /// <summary>
        /// Occurs when the forgotten knowledge from the source provider is not
        /// contained in the current knowledge of the destination provider.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Occurs when an out-of-date synchronization destination is detected.
        /// This occurs when the forgotten knowledge from the source indicates 
        /// that the destination provider contains active items that have been 
        /// deleted from the source replica, and have also had their tombstones
        /// removed from the source metadata. To repair the destination replica 
        /// in this case, all source items are enumerated and applied to the 
        /// destination.
        /// </para>
        /// </remarks>
        private void FullEnumerationNeededCallback(object sender, FullEnumerationNeededEventArgs e)
        {
            e.Action = FullEnumerationAction.Full;
        }

        /// <summary>
        /// Occurs when a synchronization provider sets a recoverable error 
        /// while an item is being loaded or saved.
        /// </summary>
        private void ItemChangeSkippedCallback(object sender, ItemChangeSkippedEventArgs e)
        {
        }

        /// <summary>
        /// Occurs before an item is changed.
        /// </summary>
        private void ItemChangingCallback(object sender, ItemChangingEventArgs e)
        {
        }

        /// <summary>
        /// Specify the resolution policy when a conflict is found between a 
        /// local and remote provider.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method's goal is to either select the source as the winner, or 
        /// to skip the change.  Obra is biased towards the local provider (the
        /// user's hard drive), and prevents overwriting of user data.  There
        /// may come a time when a knob (--option) will be created to support
        /// overwriting pre-existing user data.
        /// </para>
        /// </remarks>
        private void ItemConflictingCallback(object sender, ItemConflictingEventArgs e)
        {
            e.SetResolutionAction(ConflictResolutionAction.Merge);
            //// XXX: these assumptions _must_ change if the direction is anything 
            //// other than upload.
            ////  * source is currently the local provider.
            ////  * target is currently the remote provider.
            //MediaItemMetadata source = this.LocalProvider[e.SourceChange.ItemId];
            //MediaItemMetadata target = this.RemoteProvider[e.DestinationChange.ItemId];

            //// default resolution is to skip the change

            //// If the target does not exist the source wins.
            //// If the source and target checksum do not match, and the source is
            //// newer than the target the source wins.
            //if (target == null || 
            //   (source.Checksum != target.Checksum && source.LastModifiedUtc > target.LastModifiedUtc) ||
            //    source.Checksum == target.Checksum)
            //{
            //    e.SetResolutionAction(ConflictResolutionAction.SourceWins);
            //}
        }

        /// <summary>
        /// Items the constraint callback.
        /// </summary>
        private void ItemConstraintCallback(object sender, ItemConstraintEventArgs e)
        {
            e.SetResolutionAction(ConstraintConflictResolutionAction.Merge);
        }

        /// <summary>
        /// Fires every time progress is made, such as the calling of an
        /// existing callback.
        /// </summary>
        private void ProgressChangedCallback(object sender, SyncStagedProgressEventArgs e)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="provider"></param>
        private void RegisterEventHandlers(SyncProvider provider)
        {
            provider.DestinationCallbacks.FullEnumerationNeeded += this.FullEnumerationNeededCallback;
            provider.DestinationCallbacks.ItemChangeSkipped += this.ItemChangeSkippedCallback;
            provider.DestinationCallbacks.ItemChanging += this.ItemChangingCallback;
            provider.DestinationCallbacks.ItemConflicting += this.ItemConflictingCallback;
            provider.DestinationCallbacks.ItemConstraint += this.ItemConstraintCallback;
            provider.DestinationCallbacks.ProgressChanged += this.ProgressChangedCallback;
        }

        private SyncOperationStatistics statistics;

        /// <summary>
        /// Statistics for a synchronization run.
        /// </summary>
        public SyncOperationStatistics Statistics
        {
            get { return statistics; }
        }

        /// <summary>
        /// Synchronize between the local and remote provider.
        /// </summary>
        /// <param name="syncDirectionOrder">
        /// Order of synchronization between providers.
        /// </param>
        private void Synchronize(SyncDirectionOrder syncDirectionOrder)
        {
            this.LocalProvider.Configuration.ConflictResolutionPolicy =
                ConflictResolutionPolicy.ApplicationDefined;
            this.RemoteProvider.Configuration.ConflictResolutionPolicy =
                ConflictResolutionPolicy.ApplicationDefined;

            // Cleanup any tombstones before synchronizing.  This simplifies
            // the synchronization agent.
            this.LocalProvider.CleanupTombstones(TimeSpan.FromSeconds(0));
            this.RemoteProvider.CleanupTombstones(TimeSpan.FromSeconds(0));

            SyncOrchestrator agent = new SyncOrchestrator()
            {
                Direction = syncDirectionOrder,
                LocalProvider = this.LocalProvider,
                RemoteProvider = this.RemoteProvider,
            };

            this.statistics = agent.Synchronize();
        }

        /// <summary>
        /// Synchronize from the remote to local provider.
        /// </summary>
        public void Download()
        {
            this.Synchronize(SyncDirectionOrder.Download);
        }

        /// <summary>
        /// Synchronize from the local to remote provider.
        /// </summary>
        public void Upload()
        {
            this.Synchronize(SyncDirectionOrder.Upload);
        }

        #region IDisposable Members

        public void Dispose()
        {
            this.LocalProvider.Dispose();
            this.RemoteProvider.Dispose();
        }

        #endregion
    }
}
